#include "GPGPU.h"
#include "Global.h"
#include <iostream>
#include <fstream>

GPGPU::GPGPU()
{
    InitializeOCL();
}

GPGPU::~GPGPU()
{
    delete A;

    delete context;
    delete queue;
    delete kernel_gauss;
    delete kernel_podzial;
    delete program_podzial;
    delete program_gauss;

}

const cl::Program::Sources GPGPU::AddFile(const std::string& file)
{
    cl::Program::Sources sources;
    std::ifstream file_ilo(file.c_str());
    if (!file_ilo.is_open())
    {
        std::cout << "Nie mozna otworzyc pliku: " << file << std::endl;
        exit(1);
    }
    std::string *source_code_ilo = new std::string(std::istreambuf_iterator<char>(file_ilo), (std::istreambuf_iterator<char>()));

    sources.push_back(std::make_pair(source_code_ilo->c_str(), source_code_ilo->length() + 1));
    
    return sources;
}

int GPGPU::InitializeOCL()
{
    try
    {
        std::vector<cl::Platform> platforms;
        cl::Platform::get(&platforms);

        std::cout << "\nFound " << platforms.size() << " OpenCL platform" << (platforms.size() > 1 ? "s:" : ":") << std::endl;
        size_t id = 0;
        for (size_t i = 0; i < platforms.size(); ++i)
        {
            const cl::Platform &p = platforms[i];
            std::cout
                    //<< "OpenCL platform " << i << ':' << std::endl
                    << "\nOpenCL platform: " << p.getInfo<CL_PLATFORM_NAME > () << std::endl
                    << "\tversion: " << p.getInfo<CL_PLATFORM_VERSION > () << std::endl
                    << "\tvendor: " << p.getInfo<CL_PLATFORM_VENDOR > () << std::endl << std::endl;

            std::vector<cl::Device> devices;
            p.getDevices(CL_DEVICE_TYPE_ALL, &devices);

            //std::cout << "\t" << devices.size() << " device" << (devices.size() > 1 ? "s" : "") << " found:"
            //      << std::endl;
            for (size_t j = 0; j < devices.size(); ++j)
            {
                cl::Device &dev = devices[j];
                std::cout
                        //<< "\t\tOpenCL device " << j << ':' << std::endl
                        << "\tOpenCL device: " << dev.getInfo<CL_DEVICE_NAME > () << std::endl
                        << "\t\tversion: " << dev.getInfo<CL_DEVICE_VERSION > () << std::endl
                        << "\t\tvendor: " << dev.getInfo<CL_DEVICE_VENDOR > () << std::endl
                        << "\t\tmax compute units: " << dev.getInfo<CL_DEVICE_MAX_COMPUTE_UNITS > () << std::endl
                        << "\t\tmax work item sizes : ";
                for (size_t k = 0; k < dev.getInfo<CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS > () - 1; ++k)
                    std::cout << dev.getInfo<CL_DEVICE_MAX_WORK_ITEM_SIZES > ()[k] << " / ";
                std::cout << dev.getInfo<CL_DEVICE_MAX_WORK_ITEM_SIZES > ()[dev.getInfo<CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS > () - 1] << std::endl
                        << "\t\tmax work group size: " << dev.getInfo<CL_DEVICE_MAX_WORK_GROUP_SIZE > () << std::endl;
                //   << "\t\tvendor: " << dev.getInfo<CL_DEVICE_MAX_WORK_ITEM_SIZES> () << std::endl;
            }
        }

        if (platforms.empty())
            return 0;

        cl::Platform &platform = platforms[id];
        std::vector<cl::Device> devices;
        platform.getDevices(CL_DEVICE_TYPE_ALL, &devices);

        // Create a context
        cl_context_properties properties[] = {CL_CONTEXT_PLATFORM, (cl_context_properties) (platform) (), 0};
        //cl::Context context(devices, properties);
        context = new cl::Context(devices, properties);

        cl_int err;

        // Create a command queue
        //cl::CommandQueue queue(*context, devices[0], 0, &err);
        queue = new cl::CommandQueue(*context, devices[0], 0, &err); 
//        queue = new cl::CommandQueue(*context, devices[0], CL_QUEUE_PROFILING_ENABLE, &err);

        // sources = new cl::Program::Sources();

        sources_podzial = AddFile("kernels/podzial.cl");
        sources_gauss = AddFile("kernels/gauss1.cl");

        program_podzial = new cl::Program(*context, sources_podzial);
        program_gauss = new cl::Program(*context, sources_gauss);
        
        try
        {
            program_podzial->build(devices);
            program_gauss->build(devices);
        }
        catch (cl::Error& err)
        {
            std::cerr << "building failed: " << err.what() << '(' << err.err() << ')' << std::endl;
            //  std::cout << "source code: "
            //                  << std::endl << *source_code
            //                << std::endl;
            //std::cout << "build status: " << program->getBuildInfo<CL_PROGRAM_BUILD_STATUS > (devices.front())
              //      << std::endl;
            //std::cout << "build log: "
              //      << std::endl << program->getBuildInfo<CL_PROGRAM_BUILD_LOG > (devices.front())
                //    << std::endl;

            exit(1);
        }

        kernel_podzial = new cl::Kernel(*program_podzial, "podzial");
        kernel_gauss = new cl::Kernel(*program_gauss, "gauss1");
    }
    catch (cl::Error& err)
    {
        std::cerr << "caught exception: " << err.what() << '(' << err.err() << ')' << std::endl;
        exit(1);
    }

    A = new cl::Buffer(*context, CL_MEM_READ_WRITE, sizeof (double) * ((NWMAX * MWMAX)*(NWMAX + 2)));

    return 0;
}